
#include "bitmap.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void bitmap_line_fill(struct bitmap_class *bmp, unsigned char *line, uint32_t def)
{
	int i = 0, width = 0;
	if(NULL != bmp && NULL != line)
	{
		width = bmp->head.width * 3;
		for(i = 0; width > i; i++)
		{
			line[i] = 0xFF & (def >> (8 * (i % 3)));
		}
	}
}

static uint8_t *bitmap_line_new(struct bitmap_class *bmp, uint32_t def)
{
	uint8_t *line_data = NULL;
	if(NULL != bmp)
	{
		line_data = (uint8_t *)calloc(1, bmp->line_length);
		if(NULL != line_data)
		{
			if(0 != def)
			{
				bitmap_line_fill(bmp, line_data, def);
			}
			return line_data;
		}
	}
	return NULL;
}

static uint8_t *bitmap_line_get_byid(struct bitmap_class *bmp, unsigned int line_id)
{
	struct bitmap_line *node = NULL;
	int direction = bmp->head.height / 2 > line_id ? 0 : 1;
	if(NULL != bmp && NULL != bmp->first_line && bmp->head.height > line_id)
	{
		node = bmp->first_line;
		while(NULL != node && node->line_id != line_id)
		{
			if(0 == direction)
			{
				node = node->next;
			}
			else
			{
				node = node->prov;
			}
		}
		return NULL != node ? node->line_data : NULL;
	}
	return NULL;
}

static int bitmap_list_add_tail(struct bitmap_class *bmp, uint8_t *line_data)
{
	struct bitmap_line *new_line = NULL;
	if(NULL != bmp && NULL != line_data)
	{
		new_line = (struct bitmap_line *)calloc(1, sizeof(*new_line));
		
		if(NULL != new_line)
		{
			new_line->line_data = line_data;

			if(NULL == bmp->first_line)
			{
				new_line->line_id = 0;
				new_line->prov = new_line;
				new_line->next = new_line;

				bmp->first_line = new_line;
			}
			else
			{
				new_line->line_id = bmp->first_line->prov->line_id + 1;

				new_line->prov = bmp->first_line->prov;
				new_line->next = bmp->first_line;

				bmp->first_line->prov->next = new_line;
				bmp->first_line->prov = new_line;
			}
			
			return 0;
		}
	}
	return -1;
}

static int bitmap_list_dump(struct bitmap_class *bmp)
{
	int n = 0;
	struct bitmap_line *node = NULL;
	if(NULL != bmp)
	{
		node = bmp->first_line;
		if(NULL != node)
		{
			do
			{
				printf("%d: %p\t\t\t%p\t\t%p\t%p\n", n++, node, node->line_data, node->prov, node->next);
				node = node->next;
			} while(bmp->first_line != node);
			return 0;
		}
	}
	return -1;
}

static int bitmap_init_list(struct bitmap_class *bmp)
{
	uint32_t i = 0;
	uint8_t *line_data = NULL;
	for(i = 0; bmp->head.height > i; i++)
	{
		//line_data = bitmap_line_new(bmp, (0xFF << (i / 3 % 3 * 8)));
		line_data = bitmap_line_new(bmp, 0x0);
		if(NULL == line_data || 0 != bitmap_list_add_tail(bmp, line_data))
		{
			return -1;
		}
	}
	return 0;
}

static int bitmap_init_head(struct bitmap_class *bmp, uint32_t width, uint32_t height)
{
	if(NULL != bmp && 3200 > width && 1920 > height)
	{
		memset(bmp, 0, sizeof(*bmp));

		bmp->line_length = get_line_length(width);

		bmp->head.base.file_flag = 0x4D42;
		bmp->head.base.file_size = bmp->line_length * height + get_head_size(bmp);
		bmp->head.base.offset = 0x36;
		bmp->head.info_size = get_head_size(bmp) - sizeof(bmp->head.base) + sizeof(bmp->head.base.align_bit);
		bmp->head.width = width;
		bmp->head.height = height;
		bmp->head.planes = 0x1;
		bmp->head.bit_per_pixel = 0x18;
		bmp->head.compression = 0x0;
		bmp->head.data_size = bmp->line_length * height;
		
		return 0;
	}
	return -1;
}

static int bitmap_free_mem(struct bitmap_class *bmp, unsigned char **buf)
{
	if(NULL != bmp || NULL != buf || NULL != *buf)
	{
		free(*buf);
		*buf = NULL;
		bmp->p_memdup = NULL;
		return 0;
	}
	return -1;
}

static int bitmap_dup_mem(struct bitmap_class *bmp, unsigned char **buf, uint32_t *len)
{
	struct bitmap_line *line = NULL;
	unsigned char *ptmp = NULL;

	if(NULL != bmp || NULL != buf || NULL != len)
	{
		if(NULL == bmp->p_memdup)
		{
			*len = bmp->line_length * bmp->head.height + bmp->head.base.offset;
			*buf = (unsigned char *)calloc(1, *len);

			if(NULL != *buf)
			{
				bmp->p_memdup = (void **)buf;

				ptmp = *buf;

				memcpy(ptmp, get_head_addr(bmp), get_head_size(bmp));
				ptmp += get_head_size(bmp);

				line = bmp->first_line->prov;

				do
				{
					memcpy(ptmp, line->line_data, bmp->line_length);
					ptmp += bmp->line_length;
					line = line->prov;
				} while(bmp->first_line->prov != line);
			}
			return 0;
		}
	}
	return -1;
}

static int bitmap_to_file(struct bitmap_class *bmp, char *filename)
{
	struct bitmap_line *line = NULL;
	if(NULL != bmp && NULL != bmp->first_line && NULL != filename)
	{
		FILE *fp = fopen(filename, "w");
		if(NULL != fp)
		{
			fseek(fp, 0, SEEK_SET);
			fwrite(get_head_addr(bmp), get_head_size(bmp), 1, fp);

			line = bmp->first_line->prov;

			do
			{
				fwrite(line->line_data, bmp->line_length, 1, fp);
				line = line->prov;
			} while(bmp->first_line->prov != line);

			return 0;
		}
	}
	return -1;
}

static int bitmap_mem_merge(struct bitmap_class *bmp, rgb24_class *buf)
{
	int i = 0;
	uint8_t *line = NULL;

	if(NULL != bmp)
	{
		for(i = bmp->head.height - 1; 0 <= i; i--)
		{
			line = bitmap_line_get_byid(bmp, i);
			if(NULL != line)
			{
				memcpy(line, buf + bmp->head.width * 3 * i, bmp->head.width * 3);
			}
		}
		return 0;
	}
	return -1;
}

static void bitmap_init_release(struct bitmap_class **bmp)
{
	struct bitmap_line *tail = NULL;
	if(NULL != bmp && NULL != *bmp && NULL != (*bmp)->first_line)
	{
		tail = (*bmp)->first_line->prov->prov;
		(*bmp)->first_line->prov = NULL;

		do
		{
			free(tail->next->line_data);
			tail->next->line_data = NULL;
			tail->next->prov = NULL;
			tail->next->next = NULL;

			free(tail->next);
			tail->next = NULL;

			tail = tail->prov;
		} while(NULL != tail);

		free((*bmp)->first_line->line_data);
		free((*bmp)->first_line);
		free(*bmp);
		*bmp = NULL;
	}
}

struct bitmap_class *init_bmp(uint32_t width, uint32_t height)
{
	struct bitmap_class *bmp = (struct bitmap_class *)calloc(1, sizeof(*bmp));
	if(NULL != bmp)
	{
		if(0 == bitmap_init_head(bmp, width, height))
		{
			if(0 == bitmap_init_list(bmp))
			{
				bmp->getline_byid = bitmap_line_get_byid;
				bmp->padline = bitmap_line_fill;
				bmp->dump = bitmap_list_dump;
				bmp->mem_merge = bitmap_mem_merge;
				bmp->memfree = bitmap_free_mem;
				bmp->memdup = bitmap_dup_mem;
				bmp->tofile = bitmap_to_file;
				bmp->release = bitmap_init_release;

				return bmp;
			}
		}
		free(bmp);
		bmp = NULL;
	}
	return NULL;
}